package ar.edu.unq.dodb.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.code.morphia.annotations.Property;

public class ReflectionUtils {
	private static class ReflectionRuntimeException extends RuntimeException {
		private static final long serialVersionUID = -8140401662854557690L;

		public ReflectionRuntimeException(final String string) {
			super(string);
		}

		public ReflectionRuntimeException(final Throwable e) {
			super(e);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T fieldGet(final Object obj, final Field field) {
		final boolean isAccessible = field.isAccessible();
		field.setAccessible(true);
		try {
			final Object ret = field.get(obj);
			field.setAccessible(isAccessible);
			return (T) ret;
		} catch (final Exception e) {
			throw new ReflectionRuntimeException(e);
		}
	}

	public static <T> T fieldGet(final Object obj, final String named) {
		try {
			final Field declaredField = obj.getClass().getDeclaredField(named);
			return ReflectionUtils.fieldGet(obj, declaredField);
		} catch (final Exception e) {
			throw new ReflectionRuntimeException(e);
		}
	}

	public static void fieldSet(final Object obj, final Field field,
			final Object value) {
		final boolean isAccessible = field.isAccessible();
		try {
			field.set(obj, value);
			field.setAccessible(isAccessible);
		} catch (final Exception e) {
			throw new ReflectionRuntimeException(e);
		}

	}

	public static void fieldSet(final Object obj, final String named,
			final Object value) {
		try {
			ReflectionUtils.fieldSet(obj, obj.getClass()
					.getDeclaredField(named), value);
		} catch (final Exception e) {
			throw new ReflectionRuntimeException(e);
		}
	}

	public static Field[] getAllFields(final Class<?> clazz) {
		return ReflectionUtils.getAllFields(clazz, new ArrayList<Field>());
	}

	private static Field[] getAllFields(final Class<?> clazz,
			final List<Field> carrieds) {
		if (clazz != null) {
			final Field[] currents = clazz.getDeclaredFields();
			carrieds.addAll(Arrays.asList(currents));
			return ReflectionUtils
					.getAllFields(clazz.getSuperclass(), carrieds);
		} else
			return carrieds.toArray(new Field[0]);
	}

	public static Field[] getAllFields(final Object obj) {
		return ReflectionUtils.getAllFields(obj.getClass());
	}

	public static Field[] getAllFieldsAnnotatedWith(final Class<?> clazz,
			final Class<? extends Annotation> annotation) {
		final List<Field> ret = new ArrayList<Field>();
		final Field[] fields = ReflectionUtils.getAllFields(clazz);
		for (final Field field : fields) {
			if (field.isAnnotationPresent(annotation)) {
				ret.add(field);
			}
		}
		return ret.toArray(new Field[0]);
	}

	public static Field[] getAllFieldsAnnotatedWith(final Object object,
			final Class<? extends Annotation> annotation) {
		return ReflectionUtils.getAllFieldsAnnotatedWith(object.getClass(),
				annotation);
	}

	@SuppressWarnings("unchecked")
	public static <T> T getClassAnnotation(final Class<?> clazz,
			final Class<? extends Annotation> annotation) {
		if (clazz == null)
			return null;
		final Annotation ann = clazz.getAnnotation(annotation);
		if (ann == null)
			return ReflectionUtils.getClassAnnotation(clazz.getSuperclass(),
					annotation);
		else
			return (T) ann;
	}

	public static Field getFieldAnnotatedWith(final Class<?> target,
			final Class<? extends Annotation> annotation) {
		final Field[] fields = ReflectionUtils.getAllFields(target);
		for (final Field field : fields) {
			if (field.isAnnotationPresent(annotation))
				return field;
		}
		throw new ReflectionRuntimeException("No field found");
	}

	public static Object getValueOfFieldAnnotatedWith(final Object obj,
			final Class<? extends Annotation> annotation) {
		final Field field = ReflectionUtils.getFieldAnnotatedWith(
				obj.getClass(), annotation);
		return ReflectionUtils.fieldGet(obj, field);
	}

	public static boolean isPropertyAnnoted(final Field field) {
		return field.isAnnotationPresent(Property.class);
	}
}
